(function() {
    "use strict";

    var crudAPISpecTests = function crudAPISpecTests() {
        "use strict";

        // Get the colllection
        var coll = db.crud_tests;

        // Setup
        function createTestExecutor(coll, method, verifyResult) {
            return function(args) {
                // Drop collection
                coll.drop();
                // Insert test data
                var r = coll.insertMany(args.insert);
                assert.eq(args.insert.length, r.insertedIds.length);

                // Execute the method with arguments
                r = coll[method].apply(coll, args.params);
                verifyResult(args.result, r);

                // Get all the results
                var results = coll.find({}).sort({_id: 1}).toArray();

                assert.docEq(args.expected, results);
            }
        }

        function checkResultObject(first, second) {
            // Only assert on the "modifiedCount" property when write commands are enabled
            if (db.getMongo().writeMode() === 'commands') {
                assert.docEq(first, second);
            } else {
                var overrideModifiedCount = {modifiedCount: undefined};
                assert.docEq(Object.merge(first, overrideModifiedCount),
                             Object.merge(second, overrideModifiedCount));
            }
        }

        // Setup executors
        var deleteManyExecutor = createTestExecutor(coll, 'deleteMany', checkResultObject);
        var deleteOneExecutor = createTestExecutor(coll, 'deleteOne', checkResultObject);
        var bulkWriteExecutor = createTestExecutor(coll, 'bulkWrite', checkResultObject);
        var findOneAndDeleteExecutor = createTestExecutor(coll, 'findOneAndDelete',
                                                          checkResultObject);
        var findOneAndReplaceExecutor = createTestExecutor(coll, 'findOneAndReplace',
                                                           checkResultObject);
        var findOneAndUpdateExecutor = createTestExecutor(coll, 'findOneAndUpdate',
                                                          checkResultObject);
        var insertManyExecutor = createTestExecutor(coll, 'insertMany', checkResultObject);
        var insertOneExecutor = createTestExecutor(coll, 'insertOne', checkResultObject);
        var replaceOneExecutor = createTestExecutor(coll, 'replaceOne', checkResultObject);
        var updateManyExecutor = createTestExecutor(coll, 'updateMany', checkResultObject);
        var updateOneExecutor = createTestExecutor(coll, 'updateOne', checkResultObject);
        var countExecutor = createTestExecutor(coll, 'count', assert.eq);
        var distinctExecutor = createTestExecutor(coll, 'distinct', assert.eq);

        //
        // BulkWrite
        //

        bulkWriteExecutor({
          insert: [{ _id: 1, c: 1 }, { _id: 2, c: 2 }, { _id: 3, c: 3 }],
          params: [[
              { insertOne: { document: {_id: 4, a: 1 } } }
            , { updateOne: { filter: {_id: 5, a:2}, update: {$set: {a:2}}, upsert:true } }
            , { updateMany: { filter: {_id: 6,a:3}, update: {$set: {a:3}}, upsert:true } }
            , { deleteOne: { filter: {c:1} } }
            , { insertOne: { document: {_id: 7, c: 2 } } }
            , { deleteMany: { filter: {c:2} } }
            , { replaceOne: { filter: {c:3}, replacement: {c:4}, upsert:true } }]],
          result: {
            acknowledged: true, insertedCount:2,
            matchedCount:1, deletedCount: 3,
            upsertedCount:2, insertedIds : {'0' : 4, '4' : 7 }, upsertedIds : { '1' : 5, '2' : 6 }
          },
          expected: [{ "_id" : 3, "c" : 4 }, { "_id" : 4, "a" : 1 }, { "_id" : 5, "a" : 2 }, { "_id" : 6, "a" : 3 }]
        });

        bulkWriteExecutor({
          insert: [{ _id: 1, c: 1 }, { _id: 2, c: 2 }, { _id: 3, c: 3 }],
          params: [[
              { insertOne: { document: { _id: 4, a: 1 } } }
            , { updateOne: { filter: {_id: 5, a:2}, update: {$set: {a:2}}, upsert:true } }
            , { updateMany: { filter: {_id: 6, a:3}, update: {$set: {a:3}}, upsert:true } }
            , { deleteOne: { filter: {c:1} } }
            , { deleteMany: { filter: {c:2} } }
            , { replaceOne: { filter: {c:3}, replacement: {c:4}, upsert:true } }], { ordered: false }],
          result: {
            acknowledged: true, insertedCount:1, matchedCount:1, deletedCount:2, upsertedCount:2, insertedIds : {'0' : 4 }, upsertedIds : { '1' : 5, '2' : 6 }
          },
          expected: [{ "_id" : 3, "c" : 4 }, { "_id" : 4, "a" : 1 }, { "_id" : 5, "a" : 2 }, { "_id" : 6, "a" : 3 }]
        });

        // DeleteMany
        //

        // DeleteMany when many documents match
        deleteManyExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
            { _id: { $gt: 1 } }
          ],
          result: {acknowledged: true, deletedCount:2},
          expected: [{_id:1, x: 11}]
        });
        // DeleteMany when no document matches
        deleteManyExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
            { _id: 4 }
          ],
          result: {acknowledged: true, deletedCount:0},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // DeleteMany when many documents match, no write concern
        deleteManyExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
            { _id: { $gt: 1 } }, { w : 0 }
          ],
          result: {acknowledged: false},
          expected: [{_id:1, x: 11}]
        });

        //
        // DeleteOne
        //

        // DeleteOne when many documents match
        deleteOneExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
              { _id: { $gt: 1 } }
            ],
          result: {acknowledged: true, deletedCount:1},
          expected: [{_id:1, x: 11}, {_id:3, x: 33}]
        });
        // DeleteOne when one document matches
        deleteOneExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
              { _id: 2 }
            ],
          result: {acknowledged: true, deletedCount:1},
          expected: [{_id:1, x: 11}, {_id:3, x: 33}]
        });
        // DeleteOne when no documents match
        deleteOneExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
              { _id: 4 }
            ],
          result: {acknowledged: true, deletedCount:0},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // DeleteOne when many documents match, no write concern
        deleteOneExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
            { _id: { $gt: 1 } }, {w:0}
          ],
          result: {acknowledged: false},
          expected: [{_id:1, x: 11}, {_id:3, x: 33}]
        });

        //
        // FindOneAndDelete
        //

        // FindOneAndDelete when one document matches
        findOneAndDeleteExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: { $gt: 2 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:33},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}]
        });
        // FindOneAndDelete when one document matches
        findOneAndDeleteExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 2 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:22},
          expected: [{_id:1, x: 11}, {_id:3, x: 33}]
        });
        // FindOneAndDelete when no documents match
        findOneAndDeleteExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });

        //
        // FindOneAndReplace
        //

        // FindOneAndReplace when many documents match returning the document before modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: { $gt: 1 } }
              , { x: 32 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:22},
          expected: [{_id:1, x: 11}, {_id:2, x: 32}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when many documents match returning the document after modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: { $gt: 1 } }
              , { x: 32 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true }
            ],
          result: {x:32},
          expected: [{_id:1, x: 11}, {_id:2, x: 32}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when one document matches returning the document before modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 2 }
              , { x: 32 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:22},
          expected: [{_id:1, x: 11}, {_id:2, x: 32}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when one document matches returning the document after modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 2 }
              , { x: 32 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true }
            ],
          result: {x:32},
          expected: [{_id:1, x: 11}, {_id:2, x: 32}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when no documents match returning the document before modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { x: 44 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when no documents match with upsert returning the document before modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { x: 44 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, upsert:true }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x:44}]
        });
        // FindOneAndReplace when no documents match returning the document after modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { x: 44 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // FindOneAndReplace when no documents match with upsert returning the document after modification
        findOneAndReplaceExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { x: 44 }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true, upsert:true }
            ],
          result: {x:44},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 44}]
        });

        assert.throws(function() {
          coll.findOneAndReplace({a:1}, {$set:{b:1}});
        });

        //
        // FindOneAndUpdate
        //

        // FindOneAndUpdate when many documents match returning the document before modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: { $gt: 1 } }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:22},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when many documents match returning the document after modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: { $gt: 1 } }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument: true }
            ],
          result: {x:23},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when one document matches returning the document before modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 2 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: {x:22},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when one document matches returning the document after modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 2 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument: true }
            ],
          result: {x:23},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when no documents match returning the document before modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 } }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when no documents match with upsert returning the document before modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, upsert:true }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });
        // FindOneAndUpdate when no documents match returning the document after modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true }
            ],
          result: null,
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // FindOneAndUpdate when no documents match with upsert returning the document after modification
        findOneAndUpdateExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [
                { _id: 4 }
              , { $inc: { x: 1 } }
              , { projection: { x: 1, _id: 0 }, sort: { x: 1 }, returnNewDocument:true, upsert:true }
            ],
          result: {x:1},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });

        assert.throws(function() {
          coll.findOneAndUpdate({a:1}, {});
        });

        assert.throws(function() {
          coll.findOneAndUpdate({a:1}, {b:1});
        });

        //
        // InsertMany
        //

        // InsertMany with non-existing documents
        insertManyExecutor({
          insert: [{ _id:1, x:11 }],
          params: [
              [{_id: 2, x: 22}, {_id:3, x:33}]
            ],
          result: {acknowledged: true, insertedIds: [2, 3]},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // InsertMany with non-existing documents, no write concern
        insertManyExecutor({
          insert: [{ _id:1, x:11 }],
          params: [
                [{_id: 2, x: 22}, {_id:3, x:33}]
              , {w:0}
            ],
          result: {acknowledged: false},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });

        //
        // InsertOne
        //

        // InsertOne with non-existing documents
        insertOneExecutor({
          insert: [{ _id:1, x:11 }],
          params: [
              {_id: 2, x: 22}
            ],
          result: {acknowledged: true, insertedId: 2},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}]
        });
        // InsertOne with non-existing documents, no write concern
        insertOneExecutor({
          insert: [{ _id:1, x:11 }],
          params: [
              {_id: 2, x: 22}, {w:0}
            ],
          result: {acknowledged: false},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}]
        });

        //
        // ReplaceOne
        //

        // ReplaceOne when many documents match
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: { $gt: 1 } }, { x: 111 }],
          result: {acknowledged:true, matchedCount:1, modifiedCount:1},
          expected: [{_id:1, x: 11}, {_id:2, x: 111}, {_id:3, x: 33}]
        });
        // ReplaceOne when one document matches
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 1 }, { _id: 1, x: 111 }],
          result: {acknowledged:true, matchedCount:1, modifiedCount:1},
          expected: [{_id:1, x: 111}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // ReplaceOne when no documents match
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { _id: 4, x: 1 }],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // ReplaceOne with upsert when no documents match without an id specified
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { x: 1 }, {upsert:true}],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0, upsertedId: 4},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { _id: 4, x: 1 }, {upsert:true}],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0, upsertedId: 4},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified, no write concern
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { _id: 4, x: 1 }, {upsert:true, w:0}],
          result: {acknowledged:false},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });
        // ReplaceOne with upsert when no documents match with an id specified, no write concern
        replaceOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { _id: 4, x: 1 }, {upsert:true, writeConcern:{w:0}}],
          result: {acknowledged:false},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });

        assert.throws(function() {
          coll.replaceOne({a:1}, {$set:{b:1}});
        });

        //
        // UpdateMany
        //

        // UpdateMany when many documents match
        updateManyExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: { $gt: 1 } }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:2, modifiedCount:2},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 34}]
        });
        // UpdateMany when one document matches
        updateManyExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 1 }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:1, modifiedCount:1},
          expected: [{_id:1, x: 12}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // UpdateMany when no documents match
        updateManyExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // UpdateMany with upsert when no documents match
        updateManyExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { $inc: { x: 1 } }, { upsert: true }],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0, upsertedId: 4},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });
        // UpdateMany with upsert when no documents match, no write concern
        updateManyExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { $inc: { x: 1 } }, { upsert: true, w: 0 }],
          result: {acknowledged:false},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id:4, x: 1}]
        });

        assert.throws(function() {
          coll.updateMany({a:1}, {});
        });

        assert.throws(function() {
          coll.updateMany({a:1}, {b:1});
        });

        //
        // UpdateOne
        //

        // UpdateOne when many documents match
        updateOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: { $gt: 1 } }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:1, modifiedCount:1},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });
        // UpdateOne when one document matches
        updateOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 1 }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:1, modifiedCount:1},
          expected: [{_id:1, x: 12}, {_id:2, x: 22}, {_id:3, x: 33}]
        });
        // UpdateOne when no documents match
        updateOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { $inc: { x: 1 } }],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}]
        });

        // UpdateOne with upsert when no documents match
        updateOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: 4 }, { $inc: { x: 1 } }, {upsert:true}],
          result: {acknowledged:true, matchedCount:0, modifiedCount:0, upsertedId: 4},
          expected: [{_id:1, x: 11}, {_id:2, x: 22}, {_id:3, x: 33}, {_id: 4, x: 1}]
        });
        // UpdateOne when many documents match, no write concern
        updateOneExecutor({
          insert: [{ _id: 1, x: 11 }, { _id: 2, x: 22 }, { _id:3, x:33 }],
          params: [{ _id: { $gt: 1 } }, { $inc: { x: 1 } }, {w:0}],
          result: {acknowledged:false},
          expected: [{_id:1, x: 11}, {_id:2, x: 23}, {_id:3, x: 33}]
        });

        assert.throws(function() {
          coll.updateOne({a:1}, {});
        });

        assert.throws(function() {
          coll.updateOne({a:1}, {b:1});
        });

        //
        // Count
        //

        // Simple count of all elements
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [{}],
          result: 3,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple count no arguments
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [],
          result: 3,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple count filtered
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [{_id: {$gt: 1}}],
          result: 2,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple count of all elements, applying limit
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [{}, {limit:1}],
          result: 1,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple count of all elements, applying skip
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [{}, {skip:1}],
          result: 2,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple count no arguments, applying hint
        countExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: [{}, {hint: { "_id": 1}}],
          result: 3,
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });

        //
        // Distinct
        //

        // Simple distinct of field x no filter
        distinctExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: ['x'],
          result: [11, 22, 33],
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple distinct of field x
        distinctExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: ['x', {}],
          result: [11, 22, 33],
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple distinct of field x filtered
        distinctExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: ['x', {x: { $gt: 11 }}],
          result: [22, 33],
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });
        // Simple distinct of field x filtered with maxTimeMS
        distinctExecutor({
          insert: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }],
          params: ['x', {x: { $gt: 11 }}, {maxTimeMS:100000}],
          result: [22, 33],
          expected: [{ _id: 1, x:11 }, { _id: 2, x:22 }, { _id: 3, x:33 }]
        });

        //
        // Find
        //

        coll.deleteMany({});
        // Insert all of them
        coll.insertMany([{a:0, b:0}, {a:1, b:1}]);

        // Simple projection
        var result = coll.find({}).sort({a:1}).limit(1).skip(1).projection({_id:0, a:1}).toArray();
        assert.docEq(result, [{a:1}]);

        // Simple tailable cursor
        var cursor = coll.find({}).sort({a:1}).tailable();
        assert.eq(34, (cursor._options & ~DBQuery.Option.slaveOk));
        var cursor = coll.find({}).sort({a:1}).tailable(false);
        assert.eq(2, (cursor._options & ~DBQuery.Option.slaveOk));

        // Check modifiers
        var cursor = coll.find({}).modifiers({$hint:'a_1'});
        assert.eq('a_1', cursor._query['$hint']);

        // allowPartialResults
        var cursor = coll.find({}).allowPartialResults();
        assert.eq(128, (cursor._options & ~DBQuery.Option.slaveOk));

        // noCursorTimeout
        var cursor = coll.find({}).noCursorTimeout();
        assert.eq(16, (cursor._options & ~DBQuery.Option.slaveOk));

        // oplogReplay
        var cursor = coll.find({}).oplogReplay();
        assert.eq(8, (cursor._options & ~DBQuery.Option.slaveOk));

        //
        // Aggregation
        //

        coll.deleteMany({});
        // Insert all of them
        coll.insertMany([{a:0, b:0}, {a:1, b:1}]);

        // Simple aggregation with useCursor
        var result = coll.aggregate([{$match: {}}], {useCursor:true}).toArray();
        assert.eq(2, result.length);

        // Simple aggregation with batchSize
        var result = coll.aggregate([{$match: {}}], {batchSize:2}).toArray();
        assert.eq(2, result.length);

        // Drop collection
        coll.drop();
        coll.ensureIndex({a:1}, {unique:true})

        // Should throw duplicate key error
        assert.throws(function() {
          coll.insertMany([{a:0, b:0}, {a:0, b:1}])
        });

        assert(coll.findOne({a:0, b:0}) != null);
        assert.throws(function() {
          coll.insertOne({a:0, b:0})
        });

        assert.throws(function() {
          coll.updateOne({b:2}, {$set: {a:0}}, {upsert:true});
        });

        assert.throws(function() {
          coll.updateMany({b:2}, {$set: {a:0}}, {upsert:true});
        });

        assert.throws(function() {
          coll.deleteOne({$invalidFieldName:{a:1}});
        });

        assert.throws(function() {
          coll.deleteMany({$set:{a:1}});
        });

        assert.throws(function() {
          coll.bulkWrite([
            { insertOne: { document: { _id: 4, a: 0 } } }
          ]);
        });
    }

    crudAPISpecTests();
})();
